Learning Domain Driven Design - 15. Event-Driven Architecture
マイクロサービス、イベントドリブンアーキテクチャは分散システムで広く採用されている
イベントドリブンはDDDと密接に関連しているが、DDDのイベントをそのまま利用して良いのだろうか
Event-Driven Architecture
コンポーネントがイベントのメッセージを交換することで、非同期に互いに通信するアーキテクチャ
イベントソーシングとは異なるので気をつけること
イベントドリブン
サービス間の通信
イベントソーシング
状態の変更をイベントとして残しておく方法
サービス内で発生するもの
サービスを他のコンポーネントと統合することを意図していない
Events
メッセージは2種類ある
Events: 既に発生した変更を記述する
受信者はキャンセルできない。取り消すには補償アクションを発行するのみ
Commands: 実行する必要がある操作を記述する
ターゲットから拒否される可能性がある
構造
典型的なイベントのスキーマ
metadata
type
ID
timestamp
payload
イベントの種類
1. Event-notification (通知)
ビジネスドメインの変更に関するメッセージ
e.g. PaycheckGenerated, CampaignPublished
ただし、サブスクライバがイベントに反応するための情報をすべて伝えるべきではない
セキュリティ
詳細情報は認証を挟んで明示的にクエリを投げることで、インフラ上で機密情報が共有されることを防ぐ
並行性
非同期なので、情報がサブスクライバに来たとき古くなっている可能性がある
競合状態に敏感で最新の情報を取得する必要があるサービスなどでは、悲観的ロックと統合して重複を防ぐ
2. Event-carried state transfer (状態転送)
プロデューサの状態の変更について通知する。非同期データレプリケーションメカニズム
通知と異なり、状態の変更を反映するためのすべてのデータが含まれる
payload 例1: 変更されたエンティティのスナップショット
payload 例2: 大きなデータ構造の場合、変更されたフィールドのみを送る
各コンシューマでキャッシュが利用できるので、プロデューサが利用できなくてもコンシューマが動き続けられるように、耐障害性を上げられる
3. Domain Event
ビジネスドメインの重要なイベントを記述し、必要なデータを含んでいる
Event-notification との違い
必要なデータが含まれているので追加で情報を取得する必要はない
notification は他サービスとの統合が目的だが、ドメインイベントはビジネスドメインをモデル化するもの
Event-carried state transfer との違い
ECST はローカルキャッシュを保持するための情報を保持のが目的
code:_
eventNotification = {
"type": "marriage-recorded",
"person-id": "01b9a761",
"payload": {
"person-id": "126a7b61",
"details": "/01b9a761/marriage-data"
}
};
ecst = {
"type": "personal-details-changed",
"person-id": "01b9a761",
"payload": {
"new-last-name": "Williams"
}
};
domainEvent = {
"type": "married",
"person-id": "01b9a761",
"payload": {
"person-id": "126a7b61",
"assumed-partner-last-name": true
}
};
Distributed Big Ball of Mud 分散泥団子
一つの設計例を元に問題点を話す
https://scrapbox.io/files/65b109067a244200243829e0.png
この設計には問題点がある
1. 過度な依存
CRMのドメインイベントに対する Marketing, AdsOptimization の依存度が高すぎるため、CRMの変更が他に影響を及ぼす可能性が高まる
2. 重複
CRMのドメインイベントから Marketing, AdsOptimization の2つへ同じモデルを出している。重複や一貫性の問題がある
3. 非効率なデータフロー
AdsOptimization の処理を待ってから Reporting を走らせるため、Report のレスポンスが遅くなる
4. 強い結合
すべてがドメインイベントに強く依存しているため
5. スケーラビリティ
すべてのコンテキストが同じイベントをサブスクライブしているため、大量のイベントが発生すると全体のパフォーマンスに影響する
各コンテキストの責任と役割を明確にし、適切なイベントタイプの選択が重要
問題点を整理する
Temporal Coupling (時間的結合)
AdsOptimization が処理を終える前に Reporting がトリガーされないようにする必要がある
Functional Coupling (機能的結合)
Marketing, AdsOptimization が顧客データの同じ射影を実装しており、変更を両方反映させる必要がある
Implementation Coupling (実装の結合)
Marketing, AdsOptimization はドメインイベントすべてを購読しているため、CRMの実装に変更があるとすべて反映させなくてはいけない
改善案
AdsOptimization がイベント通知メッセージを公開し、Reporting が必要なデータをフェッチする
射影ロジックをプロデューサ側でカプセル化する。CRMがコンシューマに必要なモデルのみを公開する
より制限されたイベントセットのみ公開し、実装の結合を解消する
https://scrapbox.io/files/65b1090989674a00231d3af4.png
設計のヒューリスティクス
最悪の事態を想定する
ネットワーク遅延、サーバー障害、イベント順序の不整合、重複など予期しない問題が発生する前提で
公開イベントと非公開イベントの使い分け
異なるイベントタイプを利用し、公開するインターフェースを最小限に抑える
実装の詳細を公開しすぎないように
整合性の要件を確認
最終的に整合性のあるデータが必要なら、イベント通知を利用して最新の状態をフェッチさせる
まとめ
3つのイベントタイプを紹介した
1. イベント通知
通知するが、追加情報は明示的にクエリする
2. 状態通知
スナップショットを送る。データレプリケーションメカニズム
3. ドメインイベント
ビジネスドメインでのイベントを説明する
不適切なイベントを利用すると大きな泥団子になってしまう。整合性の確認、実装のカプセル化、イベントの使い分けによって適切な実装をしていくことが大事
他にも、技術的な問題が発生してもシステムがメッセージをちゃんと配信することを確認すること